#include "LuaScript.h"
#include "Error.h"

namespace Engine
{
	LuaScript::LuaScript()
	{
		m_luaState = luaL_newstate();
		luaL_openlibs(m_luaState);
	}

	bool LuaScript::Load(const std::string& fileName)
	{
		if(luaL_loadfile(m_luaState, fileName.c_str()) != 0)
		{
			Error("Unable to open Lua Script file " + fileName + ": " + lua_tostring(m_luaState, -1));
			return false;
		}
		m_name = fileName;
		return true;
	}

	bool LuaScript::Load(rapidxml::xml_node <>* node)
	{
		if(!node ->first_node("FileName"))
		{
			Error("Unable to find Lua Script FileName in xml file");
			return false;
		}
		return Load(node ->first_node("FileName") ->value());
	}

	void LuaScript::IncludeUserLibrary(LuaLibrary* l)
	{
		l ->Register(m_luaState);
	}

	bool LuaScript::RunScript()
	{
		if(lua_pcall(m_luaState, 0, 0, 0) != 0)
		{
			Error("Error found in Lua Script File " + m_name + ": " + lua_tostring(m_luaState, -1));
			return false;
		}
		return true;
	}

	bool LuaScript::GetGlobal(const std::string& varName, float& f)
	{
		lua_getglobal(m_luaState, varName.c_str());

		if(!lua_isnumber(m_luaState, -1))
		{
			Error(varName + " in LuaScript " + m_name + " is not a float");
			return false;
		}
		f = (float)lua_tonumber(m_luaState, -1);
		return true;
	}

	bool LuaScript::GetGlobal(const std::string& varName, bool& b)
	{
		lua_getglobal(m_luaState, varName.c_str());

		if(!lua_isboolean(m_luaState, -1))
		{
			Error(varName + " in LuaScript " + m_name + " is not a boolean");
			return false;
		}
		b = lua_toboolean(m_luaState, -1) ? true : false;
		return true;
	}

	bool LuaScript::GetGlobal(const std::string& varName, std::string& s)
	{
		lua_getglobal(m_luaState, varName.c_str());

		if(!lua_isstring(m_luaState, -1))
		{
			Error(varName + " in LuaScript " + m_name + " is not a string");
			return false;
		}
		s = lua_tostring(m_luaState, -1);
		return true;
	}

	bool LuaScript::GetGlobal(const std::string& varName, int& i)
	{
		lua_getglobal(m_luaState, varName.c_str());

		if(!lua_isnumber(m_luaState, -1))
		{
			Error(varName + " in LuaScript " + m_name + " is not an int");
			return false;
		}
		i = lua_tointeger(m_luaState, -1);
		return true;
	}

	void LuaScript::CreateNewTable(const std::string& name)
	{
		lua_newtable(m_luaState);
		lua_setglobal(m_luaState, name.c_str());
	}

	bool LuaScript::GetTableField(const std::string& table, const std::string& field, float& f)
	{
		lua_getglobal(m_luaState, table.c_str());

		if(!lua_istable(m_luaState, -1))
		{
			Error(table + " in LuaScript " + m_name + " is not a table");
			return false;
		}

		lua_pushstring(m_luaState, field.c_str());
		lua_gettable(m_luaState, -2); 

		if(!lua_isnumber(m_luaState, -1))
		{
			Error(field + " in " + table + " LuaScript " + m_name + " is not a float");
			return false;
		}

		f = (float)lua_tonumber(m_luaState, -1);
		lua_pop(m_luaState, 1); 

		return true;
	}

	bool LuaScript::GetTableField(const std::string& table, const std::string& field, bool& b)
	{
		lua_getglobal(m_luaState, table.c_str());

		if(!lua_istable(m_luaState, -1))
		{
			Error(table + " in LuaScript " + m_name + " is not a table");
			return false;
		}
				
		lua_pushstring(m_luaState, field.c_str());
		lua_gettable(m_luaState, -2); 

		if(!lua_isboolean(m_luaState, -1))
		{
			Error(field + " in " + table + " LuaScript " + m_name + " is not a boolean");
			return false;
		}

		b = lua_toboolean(m_luaState, -1) ? true : false;
		lua_pop(m_luaState, 1); 

		return true;
	}

	bool LuaScript::GetTableField(const std::string& table, const std::string& field, std::string& s)
	{
		lua_getglobal(m_luaState, table.c_str());

		if(!lua_istable(m_luaState, -1))
		{
			Error(table + " in LuaScript " + m_name + " is not a table");
			return false;
		}		

		lua_pushstring(m_luaState, field.c_str());
		lua_gettable(m_luaState, -2); 

		if(!lua_isstring(m_luaState, -1))
		{
			Error(field + " in " + table + " LuaScript " + m_name + " is not a string");
			return false;
		}

		s = lua_tostring(m_luaState, -1);
		lua_pop(m_luaState, 1); 

		return true;
	}

	bool LuaScript::GetTableField(const std::string& table, const std::string& field, int& i)
	{
		lua_getglobal(m_luaState, table.c_str());

		if(!lua_istable(m_luaState, -1))
		{
			Error(table + " in LuaScript " + m_name + " is not a table");
			return false;
		}
					
		lua_pushstring(m_luaState, field.c_str());
		lua_gettable(m_luaState, -2); 

		if(!lua_isnumber(m_luaState, -1))
		{
			Error(field + " in " + table + " LuaScript " + m_name + " is not an int");
			return false;
		}

		i = lua_tointeger(m_luaState, -1);
		lua_pop(m_luaState, 1); 

		return true;
	}

	bool LuaScript::SetTableField(const std::string& table, const std::string& field, float f)
	{
		lua_getglobal(m_luaState, table.c_str());

		if(!lua_istable(m_luaState, -1))
		{
			Error(table + " in LuaScript " + m_name + " is not a table");
			return false;
		}

		lua_pushstring(m_luaState, field.c_str());
		lua_pushnumber(m_luaState, (double)f);
		lua_settable(m_luaState, -3);

		return true;
	}

	bool LuaScript::SetTableField(const std::string& table, const std::string& field, bool b)
	{
		lua_getglobal(m_luaState, table.c_str());

		if(!lua_istable(m_luaState, -1))
		{
			Error(table + " in LuaScript " + m_name + " is not a table");
			return false;
		}

		lua_pushstring(m_luaState, field.c_str());
		lua_pushboolean(m_luaState, (bool)b);
		lua_settable(m_luaState, -3);

		return true;
	}

	bool LuaScript::SetTableField(const std::string& table, const std::string& field, std::string& s)
	{
		lua_getglobal(m_luaState, table.c_str());

		if(!lua_istable(m_luaState, -1))
		{
			Error(table + " in LuaScript " + m_name + " is not a table");
			return false;
		}		

		lua_pushstring(m_luaState, field.c_str());
		lua_pushstring(m_luaState, s.c_str());
		lua_settable(m_luaState, -3);

		return true;
	}

	bool LuaScript::SetTableField(const std::string& table, const std::string& field, int i)
	{
		lua_getglobal(m_luaState, table.c_str());

		if(!lua_istable(m_luaState, -1))
		{
			Error(table + " in LuaScript " + m_name + " is not a table");
			return false;
		}
			
		lua_pushstring(m_luaState, field.c_str());
		lua_pushinteger(m_luaState, i);
		lua_settable(m_luaState, -3);

		return true;
	}

	void LuaScript::GetLuaFunction(const std::string& name)
	{
		lua_getglobal(m_luaState, name.c_str());
	}

	void LuaScript::Push(int v)
	{
		lua_pushinteger(m_luaState, v);
	}

	void LuaScript::Push(float v)
	{
		lua_pushnumber(m_luaState, (float)v);
	}

	void LuaScript::Push(bool v)
	{
		lua_pushboolean(m_luaState, v);
	}

	void LuaScript::Push(const std::string& v)
	{
		lua_pushstring(m_luaState, v.c_str());
	}

	bool LuaScript::CallLuaFunction(unsigned int numParams, unsigned int numReturns)
	{
		if(lua_pcall(m_luaState, numParams, numReturns, 0) != 0)
		{
			Error("Error calling LuaFunction in LuaScript " + m_name + ": " + lua_tostring(m_luaState, -1));
			return false;
		}
		return true;
	}

	bool LuaScript::GetAndPop(int& v)
	{
		 if(!lua_isnumber(m_luaState, -1))
		 {
			 Error("LuaFunction from LuaScript " + m_name + " does not return a number");
			 return false;
		 }
		 v = lua_tointeger(m_luaState, -1);
		 lua_pop(m_luaState, 1); 

		 return true;
	}

	bool LuaScript::GetAndPop(float& v)
	{
		 if(!lua_isnumber(m_luaState, -1))
		 {
			 Error("LuaFunction from LuaScript " + m_name + " does not return a number");
			 return false;
		 }
		 v = (float)lua_tonumber(m_luaState, -1);
		 lua_pop(m_luaState, 1); 

		 return true;
	}

	bool LuaScript::GetAndPop(bool& v)
	{
		if(!lua_isboolean(m_luaState, -1))
		 {
			 Error("LuaFunction from LuaScript " + m_name + " does not return a boolean");
			 return false;
		 }
		 v = lua_toboolean(m_luaState, -1) ? true : false;
		 lua_pop(m_luaState, 1); 

		 return true;
	}

	bool LuaScript::GetAndPop(std::string& v)
	{
		if(!lua_isstring(m_luaState, -1))
		{
			 Error("LuaFunction from LuaScript " + m_name + " does not return a string");
			 return false;
		}
		v = lua_tostring(m_luaState, -1);
		lua_pop(m_luaState, 1); 

		return true;
	}

	void LuaScript::PushCPPFunction(const std::string& name, lua_CFunction func)
	{
		lua_pushcfunction(m_luaState,func);
		lua_setglobal(m_luaState, name.c_str());
	}

	LuaScript::~LuaScript()
	{
		lua_close(m_luaState);
	}
}